using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using NBear.Common.JSON;
using System.Runtime.Serialization.Formatters.Binary;

namespace NBear.Common
{
    public enum EntitySerializeType
    {
        Xml,
        Json
    }

    public class EntitySerializer
    {
        private EntitySerializeType type;

        public EntitySerializer()
            : this(EntitySerializeType.Xml)
        {
        }

        public EntitySerializer(EntitySerializeType type)
        {
            this.type = type;
        }

        public string Serialize(object obj)
        {
            return type == EntitySerializeType.Xml ? SerializeHelper.SerializeEntity(obj) : SerializeHelper.JsonSerializeEntity(obj);
        }

        public string SerializeArray(Array objs)
        {
            return type == EntitySerializeType.Xml ? SerializeHelper.SerializeEntityArray(objs) : SerializeHelper.JsonSerializeEntityArray(objs);
        }

        public IEntityType Deserialize<IEntityType>(string data)
            where IEntityType : IEntity
        {
            return type == EntitySerializeType.Xml ? SerializeHelper.DeserializeEntity<IEntityType>(data) : SerializeHelper.JsonDeserializeEntity<IEntityType>(data);
        }

        public IEntityType[] DeserializeArray<IEntityType>(string data)
            where IEntityType : IEntity
        {
            return type == EntitySerializeType.Xml ? 
                SerializeHelper.DeserializeEntityArray<IEntityType>(data)
                : SerializeHelper.JsonDeserializeEntityArray<IEntityType>(data);
        }
    }

    public class SerializeHelper
    {
        #region Helper Methods

        #region System Xml Serialization

        private static string RemoveXmlHeader(string xml)
        {
            if (xml.StartsWith("<?"))
            {
                return xml.Substring(xml.IndexOf("?>") + 2);
            }
            else
            {
                return xml;
            }
        }

        public static string Serialize(object obj)
        {
            if (obj == null)
            {
                return null;
            }

            if (obj.GetType() == typeof(string))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(int))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(long))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(short))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(byte))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(bool))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(decimal))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(char))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(sbyte))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(float))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(double))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(byte[]))
            {
                return Convert.ToBase64String((byte[])obj);
            }
            else if (obj.GetType() == typeof(Guid))
            {
                return obj.ToString();
            }
            else if (obj.GetType() == typeof(DateTime))
            {
                return obj.ToString();
            }
            else
            {
                XmlSerializer s = new XmlSerializer(obj.GetType());
                StringBuilder sb = new StringBuilder();
                s.Serialize(new StringWriter(sb), obj);
                return RemoveXmlHeader(sb.ToString());
            }
        }

        public static void Serialize(Stream stream, object obj)
        {
            if (obj == null)
            {
                return;
            }

            XmlSerializer s = new XmlSerializer(obj.GetType());
            s.Serialize(stream, obj);
        }

        public static string SerializeArray(Array objs)
        {
            if (objs != null && objs.Length > 0)
            {
                IEnumerator en = objs.GetEnumerator();
                en.MoveNext();
                object obj = en.Current;
                XmlSerializer s = new XmlSerializer(obj.GetType().MakeArrayType());
                StringBuilder sb = new StringBuilder();
                s.Serialize(new StringWriter(sb), objs);
                return RemoveXmlHeader(sb.ToString());
            }
            else
            {
                return null;
            }
        }

        public static void SerializeArray(Stream stream, object[] objs)
        {
            if (objs != null && objs.Length > 0)
            {
                XmlSerializer s = new XmlSerializer(objs[0].GetType().MakeArrayType());
                s.Serialize(stream, objs);
            }
            else
            {
                return;
            }
        }

        public static ReturnType Deserialize<ReturnType>(Type originType, string xml)
        {
            return (ReturnType)Deserialize(originType, xml);
        }

        public static object Deserialize(Type originType, string xml)
        {
            if (originType == typeof(string))
            {
                return xml;
            }
            else if (originType == typeof(int))
            {
                return int.Parse(xml);
            }
            else if (originType == typeof(long))
            {
                return long.Parse(xml);
            }
            else if (originType == typeof(short))
            {
                return short.Parse(xml);
            }
            else if (originType == typeof(byte))
            {
                return byte.Parse(xml);
            }
            else if (originType == typeof(bool))
            {
                return bool.Parse(xml);
            }
            else if (originType == typeof(decimal))
            {
                return decimal.Parse(xml);
            }
            else if (originType == typeof(char))
            {
                return char.Parse(xml);
            }
            else if (originType == typeof(sbyte))
            {
                return sbyte.Parse(xml);
            }
            else if (originType == typeof(float))
            {
                return float.Parse(xml);
            }
            else if (originType == typeof(double))
            {
                return double.Parse(xml);
            }
            else if (originType == typeof(byte[]))
            {
                return Convert.FromBase64String(xml);
            }
            else if (originType == typeof(Guid))
            {
                return new Guid(xml);
            }
            else if (originType == typeof(DateTime))
            {
                return DateTime.Parse(xml);
            }
            else
            {
                XmlSerializer s = new XmlSerializer(originType);
                return s.Deserialize(new StringReader(xml));
            }
        }

        #endregion

        #region Entity Serialization

        #region Xml Serialization

        private const string NULL = "<_Null />";

        internal static string SerializeEntity<IEntityType>(IEntityType obj)
            where IEntityType : IEntity
        {
            return DoSerializeEntity(obj);
        }

        internal static string SerializeEntity(object obj)
        {
            return DoSerializeEntity(obj);
        }

        private static string DoSerializeEntity(object obj)
        {
            if (obj == null)
            {
                return NULL;
            }

            Type objType = obj.GetType();

            if (objType.GetInterface("IEntity") != typeof(IEntity))
            {
                throw new NotSupportedException("Only Entities implementing NBear.Common.IEntity can be serialized!");
            }

            return string.Format("<{0}>{1}</{0}>", objType.Name, ((IEntity)obj).GetKeyValues().ToString());
        }

        internal static IEntityType DeserializeEntity<IEntityType>(string xml)
            where IEntityType : IEntity
        {
            if (string.IsNullOrEmpty(xml) || xml == NULL)
            {
                return default(IEntityType);
            }

            XmlReader reader = new XmlTextReader(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(xml)));

            return DeserializeEntity<IEntityType>(reader);
        }

        internal static IEntityType DeserializeEntity<IEntityType>(XmlReader reader) where IEntityType : IEntity
        {
            if (reader.IsEmptyElement)
            {
                reader.Skip();
                return default(IEntityType);
            }

            IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
            retObj.GetKeyValues().Load(reader);
            return retObj;
        }

        internal static string SerializeEntityArray<IEntityType>(IEntityType[] objs)
            where IEntityType : IEntity
        {
            return DoSerializeEntityArray(objs);
        }

        internal static string SerializeEntityArray(Array objs)
        {
            return DoSerializeEntityArray(objs);
        }

        private static string DoSerializeEntityArray(Array objs)
        {
            if (objs == null)
            {
                return NULL;
            }

            bool hasNonNullInstances = false;
            Type objType = null;

            foreach (object obj in objs)
            {
                if (obj != null)
                {
                    hasNonNullInstances = true;
                    objType = obj.GetType();
                    break;
                }
            }

            if (hasNonNullInstances)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append(string.Format("<ArrayOf{0}>", objType.Name));

                foreach (object obj in objs)
                {
                    sb.Append(SerializeEntity(obj));
                }

                sb.Append(string.Format("</ArrayOf{0}>", objType.Name));

                return sb.ToString();
            }
            else
            {
                return string.Format("<ArrayOf{0} />", objType.Name);
            }
        }

        internal static IEntityType[] DeserializeEntityArray<IEntityType>(string xml)
            where IEntityType : IEntity
        {
            if (string.IsNullOrEmpty(xml) || xml == NULL)
            {
                return null;
            }

            XmlReader reader = new XmlTextReader(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(xml)));

            return DeserializeEntityArray<IEntityType>(reader);
        }

        internal static IEntityType[] DeserializeEntityArray<IEntityType>(XmlReader reader) 
            where IEntityType : IEntity
        {
            if (reader.IsEmptyElement)
            {
                reader.Skip();
                return null;
            }

            List<IEntityType> retObjs = new List<IEntityType>();

            reader.ReadStartElement();

            do
            {
                retObjs.Add(DeserializeEntity<IEntityType>(reader));
            }
            while (reader.NodeType != XmlNodeType.EndElement);

            reader.ReadEndElement();

            return retObjs.ToArray();
        }

        #endregion

        #region Json Serialization

        internal static string JsonSerializeEntity<IEntityType>(IEntityType obj)
            where IEntityType : IEntity
        {
            return DoJsonSerializeEntity(obj);
        }

        internal static string JsonSerializeEntity(object obj)
        {
            return DoJsonSerializeEntity(obj);
        }

        private static string DoJsonSerializeEntity(object obj)
        {
            if (obj == null)
            {
                return "null";
            }

            if (obj.GetType().GetInterface("IEntity") != typeof(IEntity))
            {
                throw new NotSupportedException("Only Entities implementing NBear.Common.IEntity can be serialized!");
            }

            return ((IEntity)obj).GetKeyValues().ToJsonString();
        }

        internal static IEntityType JsonDeserializeEntity<IEntityType>(string sJson)
            where IEntityType : IEntity
        {
            IEntityType obj = EntityFactory<IEntityType>.CreateObject();
            obj.GetKeyValues().LoadJson(sJson);
            return obj;
        }

        internal static string JsonSerializeEntityArray<IEntityType>(IEntityType[] objs)
            where IEntityType : IEntity
        {
            return DoJsonSerializeEntityArray(objs);
        }

        internal static string JsonSerializeEntityArray(Array objs)
        {
            return DoJsonSerializeEntityArray(objs);
        }

        private static string DoJsonSerializeEntityArray(Array objs)
        {
            if (objs == null)
            {
                return "null";
            }

            bool hasNonNullInstances = false;

            foreach (object obj in objs)
            {
                if (obj != null)
                {
                    hasNonNullInstances = true;
                    break;
                }
            }

            if (hasNonNullInstances)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("[");

                int i = 0;
                foreach (object obj in objs)
                {
                    if (obj == null)
                    {
                        sb.Append("{}");
                    }
                    else
                    {
                        sb.Append(((IEntity)obj).GetKeyValues().ToJsonString());
                    }

                    if (i < objs.Length - 1)
                    {
                        sb.Append(",");
                    }

                    i++;
                }

                sb.Append("]");

                return sb.ToString();
            }
            else
            {
                return "[]";
            }
        }

        internal static IEntityType[] JsonDeserializeEntityArray<IEntityType>(string sJson)
            where IEntityType : IEntity
        {
            if (string.IsNullOrEmpty(sJson) || sJson == "null")
            {
                return null;
            }

            JSONArray json = new JSONArray(sJson);
            return JsonDeserializeEntityArray<IEntityType>(json);
        }

        internal static IEntityType[] JsonDeserializeEntityArray<IEntityType>(JSONArray json) 
            where IEntityType : IEntity
        {
            IEntityType[] retObjs = new IEntityType[json.Count];

            for (int i = 0; i < json.Count; i++)
            {
                IEntityType retObj = EntityFactory<IEntityType>.CreateObject();
                retObj.GetKeyValues().LoadJson(json.getJSONObject(i));
                retObjs[i] = retObj;
            }

            return retObjs;
        }

        #endregion

        #endregion

        #endregion
    }
}
